/*-------------------------------------------------------------------------
 *
 * jit.c
 *	  提供者独立的JIT基础设施。
 *
 * 涉及加载JIT提供者、重定向调用到JIT提供者和错误处理的代码。这里不应该有任何特定于特定JIT实现的代码。
 *
 *
 * Copyright (c) 2016-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/jit/jit.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "executor/execExpr.h"
#include "fmgr.h"
#include "jit/jit.h"
#include "miscadmin.h"
#include "utils/fmgrprotos.h"
#include "utils/resowner_private.h"

/* GUCs */
bool		jit_enabled = true;
char	   *jit_provider = NULL;
bool		jit_debugging_support = false;
bool		jit_dump_bitcode = false;
bool		jit_expressions = true;
bool		jit_profiling_support = false;
bool		jit_tuple_deforming = true;
double		jit_above_cost = 100000;
double		jit_inline_above_cost = 500000;
double		jit_optimize_above_cost = 500000;

static JitProviderCallbacks provider;
static bool provider_successfully_loaded = false;
static bool provider_failed_loading = false;


static bool fc_provider_init(void);
static bool fc_file_exists(const char *fc_name);


/*
 * SQL 级函数返回当前后端是否可用 JIT。必要时将尝试加载 JIT 提供者。
 */
Datum pg_jit_available(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(fc_provider_init());
}


/*
 * 返回 JIT 提供者是否已成功加载，缓存结果。
 */
static bool fc_provider_init(void)
{
	char		fc_path[MAXPGPATH];
	JitProviderInit fc_init;

	/* 如果未启用，则甚至不尝试加载 */
	if (!jit_enabled)
		return false;

	/*
	 * 加载失败后不要重试 - 尝试加载 JIT 提供者并不便宜。
	 */
	if (provider_failed_loading)
		return false;
	if (provider_successfully_loaded)
		return true;

	/*
	 * 检查共享库是否存在。在实际尝试加载共享库（通过 load_external_function()）之前，我们进行该检查，
	 * 因为如果共享库不可用，那将导致错误。
	 */
	snprintf(fc_path, MAXPGPATH, "%s/%s%s", pkglib_path, jit_provider, DLSUFFIX);
	elog(DEBUG1, "probing availability of JIT provider at %s", fc_path);
	if (!fc_file_exists(fc_path))
	{
		elog(DEBUG1,
			 "provider not available, disabling JIT for current session");
		provider_failed_loading = true;
		return false;
	}

	/*
	 * 如果加载函数失败，发出失败信号。我们这样做是因为
	 * load_external_function() 可能会在上述检查之后出错，例如，如果库的依赖项未安装。我们希望在这种情况下发出 ERROR 信号，以便用户得到通知，但我们不想不断重试。
	 */
	provider_failed_loading = true;

	/* 并初始化 */
	fc_init = (JitProviderInit)
		load_external_function(fc_path, "_PG_jit_provider_init", true, NULL);
	fc_init(&provider);

	provider_successfully_loaded = true;
	provider_failed_loading = false;

	elog(DEBUG1, "successfully loaded JIT provider in current session");

	return true;
}

/*
 * 重置 JIT 提供者的错误处理。这将在抛出错误后调用，并且主循环已重新建立控制。
 */
void jit_reset_after_error(void)
{
	if (provider_successfully_loaded)
		provider.reset_after_error();
}

/*
 * 释放一个 JIT 上下文所需的资源。
 */
void jit_release_context(JitContext *fc_context)
{
	if (provider_successfully_loaded)
		provider.release_context(fc_context);

	ResourceOwnerForgetJIT(fc_context->resowner, PointerGetDatum(fc_context));
	pfree(fc_context);
}

/*
 * 请求提供者对一个表达式进行 JIT 编译。
 *
 * 如果成功返回 true，失败返回 false。
 */
bool jit_compile_expr(struct ExprState *fc_state)
{
	
/*
	 * 我们可以轻松地为没有关联 PlanState（因此没有 EState）的函数创建一次性上下文。
	 * 但由于没有执行器关闭回调来释放创建的函数，它们将持续到事务结束，
	 * 在那里它们将由 resowner 机制清理。这可能导致显著的内存使用，
	 * 更糟糕的是，触发 gdb 中的某些平方行为。因此，至少在目前的情况下，
	 * 不要在这些情况下创建 JITed 函数。
	 */
	if (!fc_state->parent)
		return false;

	/* 如果根本不应该进行 JIT 编译 */
	if (!(fc_state->parent->state->es_jit_flags & PGJIT_PERFORM))
		return false;

	/* 或者如果表达式没有被 JIT 编译 */
	if (!(fc_state->parent->state->es_jit_flags & PGJIT_EXPR))
		return false;

	/* 这也考虑了 !jit_enabled */
	if (fc_provider_init())
		return provider.compile_expr(fc_state);

	return false;
}

/* 聚合 JIT 仪器信息 */
void InstrJitAgg(JitInstrumentation *fc_dst, JitInstrumentation *fc_add)
{
	fc_dst->created_functions += fc_add->created_functions;
	INSTR_TIME_ADD(fc_dst->generation_counter, fc_add->generation_counter);
	INSTR_TIME_ADD(fc_dst->inlining_counter, fc_add->inlining_counter);
	INSTR_TIME_ADD(fc_dst->optimization_counter, fc_add->optimization_counter);
	INSTR_TIME_ADD(fc_dst->emission_counter, fc_add->emission_counter);
}

static bool fc_file_exists(const char *fc_name)
{
	struct stat fc_st;

	AssertArg(fc_name != NULL);

	if (stat(fc_name, &fc_st) == 0)
		return !S_ISDIR(fc_st.st_mode);
	else if (!(errno == ENOENT || errno == ENOTDIR))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not access file \"%s\": %m", fc_name)));

	return false;
}
